/*
 * MyGWT User Extensions
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package net.mygwt.ux.client.widgets;

import net.mygwt.ui.client.Events;
import net.mygwt.ui.client.MyDOM;
import net.mygwt.ui.client.MyGWT;
import net.mygwt.ui.client.Style;
import net.mygwt.ui.client.event.BaseEvent;
import net.mygwt.ui.client.event.Listener;
import net.mygwt.ui.client.fx.Draggable;
import net.mygwt.ui.client.util.Rectangle;
import net.mygwt.ui.client.widget.Component;

import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.WidgetHelper;

/**
 * 
 * @author ggeorg
 *
 */
public class Slider extends Component {

  private double mValue = 0.0;
  private double mIncrement = 0.1;

  private int mMouseX, mMouseY;

  private Component mHandle;
  private Element mLineElem, mHandleElem;
  private Draggable mDraggable;

  public Slider() {
    this(Style.HORIZONTAL);
  }

  public Slider(final int pStyle) {
    super(pStyle);

    if ((style & Style.HORIZONTAL) != 0) {
      baseStyle = "my-ux-slider-h";
    } else {
      baseStyle = "my-ux-slider-v";
    }
  }

  public double getValue() {
    return mValue;
  }

  public void setValue(double pValue) {
    if (mValue != pValue) {
      BaseEvent be = new BaseEvent();
      be.widget = this;
      be.oldValue = new Double(mValue);
      mValue = pValue;
      if (mValue > 1.0) {
        mValue = 1.0;
      } else if (mValue < 0.0) {
        mValue = 0.0;
      } else {
        mValue = pValue;
      }
      be.value = new Double(mValue);
      fireEvent(Events.Change, be);
    }
  }

  public double getIncrement() {
    return mIncrement;
  }

  public void setIncrement(double pIncrement) {
    if (pIncrement > 1.0) {
      mIncrement = 1.0;
    } else if (pIncrement < 0.0) {
      mIncrement = 0.0;
    } else {
      mIncrement = pIncrement;
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see net.mygwt.ui.client.widget.Component#onRender()
   */
  protected void onRender() {
    setElement(DOM.createDiv());
    setStyleName(baseStyle);

    // create line
    mLineElem = DOM.createDiv();
    MyDOM.setStyleName(mLineElem, baseStyle + "-line");
    MyDOM.disableTextSelection(mLineElem, true);
    DOM.appendChild(mLineElem, DOM.createDiv());
    DOM.appendChild(getElement(), mLineElem);

    // create handle
    mHandle = new Component() {
      private boolean hovering = false;

      {
        baseStyle = Slider.this.baseStyle + "-handle";
      }

      protected void onRender() {
        setElement(DOM.createDiv());
        setStyleName(baseStyle);

        MyDOM.disableTextSelection(getElement(), true);
        Element div = DOM.createDiv();
        DOM.appendChild(getElement(), div);
        DOM.setInnerHTML(div, "&#160;");

        sinkEvents(Event.MOUSEEVENTS);
      }

      public void onBaseEvent(BaseEvent be) {
        Rectangle rect = MyDOM.getBounds(getElement());
        if (rect.contains(be.getClientX(), be.getClientY())) {
          if (!hovering) {
            hovering = true;
            onMouseOver(be);
          }
        } else {
          hovering = false;
          onMouseOut(be);
        }
      }

      protected void onMouseOut(BaseEvent be) {
        if (MyGWT.isIE || MyGWT.isIE7) {
          removeStyleName(baseStyle + "-over");
        }
      }

      protected void onMouseOver(BaseEvent be) {
        if (MyGWT.isIE || MyGWT.isIE7) {
          addStyleName(baseStyle + "-over");
        }
      }

    };

    mHandleElem = mHandle.getElement();
    DOM.appendChild(getElement(), mHandleElem);

    mDraggable = new Draggable(mHandle);
    mDraggable.updateZIndex = false;
    mDraggable.useProxy = false;
    mDraggable.container = this;

    Listener dragListener = new Listener() {
      public void handleEvent(BaseEvent pEvent) {
        switch (pEvent.type) {
          case Events.DragEnd:
            if ((style & Style.HORIZONTAL) != 0) {
              final int lw = MyDOM.getWidth(mLineElem);
              final int hl = MyDOM.getLeft(mHandleElem);
              DeferredCommand.addCommand(new Command() {
                public void execute() {
                  setValue(hl / (double) lw);
                }
              });
            } else {
              final int lh = MyDOM.getHeight(mLineElem);
              final int ht = MyDOM.getTop(mHandleElem);
              DeferredCommand.addCommand(new Command() {
                public void execute() {
                  setValue((lh - ht) / (double) lh);
                }
              });
            }
            break;
        }
      }
    };

    mDraggable.addListener(Events.DragEnd, dragListener);

    sinkEvents(Event.MOUSEEVENTS | Event.KEYEVENTS);
  }

  /*
   * (non-Javadoc)
   * 
   * @see net.mygwt.ui.client.widget.Component#onBaseEvent(net.mygwt.ui.client.event.BaseEvent)
   */
  public void onBaseEvent(BaseEvent be) {
    Rectangle rect = MyDOM.getBounds(getElement());
    if (rect.contains(be.getClientX(), be.getClientY())) {
      onMouseUp(be);
    }
    switch (be.type) {
      case Event.ONMOUSEDOWN:
        onMouseDown(be);
        break;
      case Event.ONMOUSEUP:
        onMouseUp(be);
        break;
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.google.gwt.user.client.ui.Widget#doAttachChildren()
   */
  protected void doAttachChildren() {
    if (mHandle != null) {
      WidgetHelper.doAttach(mHandle);
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see com.google.gwt.user.client.ui.Widget#doDetachChildren()
   */
  protected void doDetachChildren() {
    if (mHandle != null) {
      WidgetHelper.doDetach(mHandle);
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see net.mygwt.ui.client.widget.Component#recalculate()
   */
  public void recalculate() {
    int w = MyDOM.getWidth(getElement());
    int h = MyDOM.getHeight(getElement());
    int hw = MyDOM.getWidth(mHandleElem);
    int hh = MyDOM.getHeight(mHandleElem);
    int lw = MyDOM.getWidth(mLineElem);
    int lh = MyDOM.getHeight(mLineElem);

    // this assumes a border-box layout

    if ((style & Style.HORIZONTAL) != 0) {
      int left = (int) ((w - hw) * getValue());
      int top = (h - hh) / 2;

      MyDOM.setLeftTop(mHandleElem, left, top);

      left = hw / 2;
      top = (h - lh) / 2;

      MyDOM.setLeftTop(mLineElem, left, top);
      MyDOM.setWidth(mLineElem, Math.max(0, w - hw - 2));
      MyDOM.setWidth(DOM.getFirstChild(mLineElem), Math.max(0, w - hw - 4));

      mDraggable.constrainVertical = true;

    } else {
      int left = (w - hw) / 2;
      int top = (int) (h - hh - (h - hh) * getValue());

      MyDOM.setLeftTop(mHandleElem, left, top);

      left = (w - lw) / 2;
      top = hh / 2;

      MyDOM.setLeftTop(mLineElem, left, top);
      MyDOM.setHeight(mLineElem, Math.max(0, h - hh - 2));
      MyDOM.setHeight(DOM.getFirstChild(mLineElem), Math.max(0, h - hh - 4));

      mDraggable.constrainHorizontal = true;

    }
  }

  private Timer mTimer = new Timer() {
    public void run() {
      if ((style & Style.HORIZONTAL) != 0) {
        int w = MyDOM.getWidth(mLineElem);
        int hw = MyDOM.getWidth(mHandleElem);
        int hl = MyDOM.getLeft(mHandleElem);

        if (mMouseX > hl + hw) {
          double distance = (mMouseX - (hl + hw)) / (double) w;
          if (distance < mIncrement) {
            setValue((mMouseX - hw / 2) / (double) w);
          } else {
            setValue(getValue() + mIncrement);
          }
        } else if (mMouseX < hl) {
          double distance = (hl - mMouseX) / (double) w;
          if (distance < mIncrement) {
            setValue((mMouseX - hw / 2) / (double) w);
          } else {
            setValue(getValue() - mIncrement);
          }
        }
      } else {
        int h = MyDOM.getHeight(mLineElem);
        int hh = MyDOM.getHeight(mHandleElem);
        int ht = MyDOM.getTop(mHandleElem);

        if (mMouseY > ht + hh) {
          double distance = (mMouseY - (ht + hh)) / (double) h;
          if (distance < mIncrement) {
            setValue((h - (mMouseY - hh / 2)) / (double) h);
          } else {
            setValue(getValue() - mIncrement);
          }
        } else if (mMouseY < ht) {
          double distance = (ht - mMouseY) / (double) h;
          if (distance < mIncrement) {
            setValue((h - (mMouseY - hh / 2)) / (double) h);
          } else {
            setValue(getValue() + mIncrement);
          }
        }
      }
      recalculate();
    }
  };

  protected void onMouseDown(BaseEvent be) {
    int w = MyDOM.getWidth(mLineElem);
    int h = MyDOM.getHeight(mLineElem);
    int hw_2 = MyDOM.getWidth(mHandleElem) / 2;
    int hh_2 = MyDOM.getHeight(mHandleElem) / 2;

    mMouseX = be.getClientX() - getAbsoluteLeft();
    if (mMouseX > w + hw_2) {
      mMouseX = w + hw_2;
    } else if (mMouseX < hw_2) {
      mMouseX = hw_2;
    }

    mMouseY = be.getClientY() - getAbsoluteTop();
    if (mMouseY > h + hh_2) {
      mMouseY = h + hh_2;
    } else if (mMouseY < hh_2) {
      mMouseY = hh_2;
    }

    mTimer.scheduleRepeating(100);
  }

  protected void onMouseUp(BaseEvent be) {
    mTimer.cancel();
  }
}
